package Structural.Proxy.aop.SpringAOP;

import java.io.IOException;
import java.lang.reflect.Method;
import java.util.Calendar;


import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import com.alibaba.fastjson.JSON;


/**
 * 例子来自以下两篇文章中例子的整合：
 * https://www.cnblogs.com/hongmoshui/p/10694535.html
 * https://blog.csdn.net/weixin_40677588/article/details/100580159
 */


/**
 * aop切面类中的@Pointcut的用法：

 格式：

 execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern)throws-pattern?)
 括号中各个pattern分别表示：

 修饰符匹配（modifier-pattern?）
 返回值匹配（ret-type-pattern）可以为*表示任何返回值,全路径的类名等
 类路径匹配（declaring-type-pattern?）
 方法名匹配（name-pattern）可以指定方法名 或者 *代表所有, set* 代表以set开头的所有方法
 参数匹配（(param-pattern)）可以指定具体的参数类型，多个参数间用“,”隔开，各个参数也可以用“*”来表示匹配任意类型的参数，如(String)表示匹配一个String参数的方法；(*,String) 表示匹配有两个参数的方法，第一个参数可以是任意类型，而第二个参数是String类型；可以用(..)表示零个或多个任意参数
 异常类型匹配（throws-pattern?）
 其中后面跟着“?”的是可选项
 */

/**
 * 注解方式声明aop
 * 1.用@Aspect注解将类声明为切面(如果用@Component("")注解注释为一个bean对象，那么就要在spring配置文件中开启注解扫描，<context:component-scan base-package="com.cjh.aop2"/>
 *      否则要在spring配置文件中声明一个bean对象)
 * 2.在切面需要实现相应方法的前面加上相应的注释，也就是通知类型。
 * 3.此处有环绕通知，环绕通知方法一定要有ProceedingJoinPoint类型的参数传入，然后执行对应的proceed()方法，环绕才能实现。
 * */


/**
 * 日志注解的切面类
 * @author 洪墨水
 */
@Aspect
@Component
public class LogAspect
{
    /**
     * 日志记录log
     */
    public transient Logger log = LoggerFactory.getLogger(getClass());

    /**
     * 环绕通知
     * @param point 切点
     * @return
     */
//    @Around("getValuePointCut()")
    @Around("execution(* Structural.Proxy.aop.SpringAOP.impl.*.*(..))")
    public Object aroundBase(ProceedingJoinPoint point)
    {
        System.out.println("proceed GW Interface start");

        RecordMessage recordMessage = new RecordMessage();
        Long startTime = System.currentTimeMillis();
        Object object = null;

        try
        {
            /* 记录下当前时间 作为请求起始时间 */
            recordMessage.setRequestTime(TimeUtil.getLongDateString(Calendar.getInstance().getTime()));

            /* 获取请求的信息 */
            getRequestParams(point, recordMessage);

            /* 执行请求的方法 */
            object = point.proceed();

            /* 记录下当前时间 作为响应时间 */
            recordMessage.setResponseTime(TimeUtil.getLongDateString(Calendar.getInstance().getTime()));

            /* 记录响应参数 */
            recordMessage.setResponseParames(object == null ? "" : JSON.toJSONString(object));
        }
        catch (Throwable e)
        {
            System.out.println("proceed GW Interface throwable, "+ e);
            log.warn("proceed GW Interface throwable, ", e);
        }
        finally
        {
            Long endTime = System.currentTimeMillis();
            recordMessage.setCostTime(endTime - startTime);
            /* 记录接口日志 */
            log.info(recordMessage.toString());
            System.out.println(recordMessage.toString());
        }
        return object;
    }

    /**
     * 从切点中解析出该切点对应的方法
     * @param point point
     * @throws ClassNotFoundException
     * @throws IOException
     * @author 洪墨水
     */
    private void getRequestParams(ProceedingJoinPoint point, RecordMessage recordMessage) throws ClassNotFoundException, IOException
    {
        /* 类名 */
        String targetObject = point.getTarget().getClass().getName();
        /* 方法名 */
        String methodName = point.getSignature().getName();

        recordMessage.setTargetObject(targetObject);
        recordMessage.setMethod(methodName);

        Object[] args = point.getArgs();

        Class<?> targetClass = Class.forName(targetObject);

        Method[] methods = targetClass.getMethods();

        StringBuilder requestBuilder = new StringBuilder(0);

        /**
         * 遍历方法 获取能与方法名相同且请求参数个数也相同的方法
         */
        for (Method method : methods)
        {
            if (!method.getName().equals(methodName))
            {
                continue;
            }

            Class<?>[] classes = method.getParameterTypes();

            if (classes.length != args.length)
            {
                continue;
            }

            for (int index = 0; index < classes.length; index++)
            {
                requestBuilder.append(args[index] == null ? "" : JSON.toJSONString(args[index]));
            }

            recordMessage.setRequestParames(requestBuilder.toString());
        }

        return;
    }

    /**
     * 切点
     */
    @Pointcut(value = "execution(* Structural.Proxy.aop.SpringAOP.impl.*.*(..))")
    public void getValuePointCut()
    {
    }

    /**
     * * 前置通知(注解中的sayings()方法，其实就是上面定义pointcut切点注解所修饰的方法名，那只是个代理对象,不需要写具体方法，
     * 相当于xml声明切面的id名，如下，相当于id="embark",用于供其他通知类型引用)
     * <aop:config>
     *     <aop:aspect ref="mistrel">
     *          <!-- 定义切点 -->
     *<aop:pointcut expression="execution(* *.saying(..))" id="embark"/>
     *<!-- 声明前置通知 (在切点方法被执行前调用) -->
     *             <aop:before method="beforSay" pointcut-ref="embark"/>
     *<!-- 声明后置通知 (在切点方法被执行后调用) -->
     *<aop:after method="afterSay" pointcut-ref="embark"/>
     *</aop:aspect>
     *</aop:config>
     */
    @Before("getValuePointCut()")
    public void before(){
    System.out.println("注解类型前置通知");
    }


    /**
     * 后置通知
     */
    @After(value = "getValuePointCut()")
    public void after()
    {
        System.out.println("注解类型后置通知");
    }

}
